package demo.messaging.server;

import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import demo.messaging.common.AbstractMessage;
import demo.messaging.common.EnumUserStatus;
import demo.messaging.common.IMessageService;
import demo.messaging.common.ServiceException;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class MessageServiceImpl extends RemoteServiceServlet implements
		IMessageService {

	public MessageServiceImpl() {

	}

	/**
	 * Obtain UserContext of the session owner from ServletContext
	 * and update the last access time.
	 * 
	 * @param userName
	 * @return user context
	 * @throw ServiceException if the user context is expired.
	 */
	private UserContext accessUserContext() throws ServiceException {

		String userName = getUserName();
		if (userName == null) {
			throw new ServiceException(ServiceException.LOST_CONNECTION);
		}
		UserContext c = getUserContext(userName);
		if (null == c) {
			throw new ServiceException(ServiceException.LOST_CONNECTION);
		}

		c.updateLastInteractionTime();
		return c;
	}

	public List<? extends AbstractMessage> getMessages()
			throws ServiceException {

		UserContext c = accessUserContext();
		List<? extends AbstractMessage> result = c.retrieveMessages();

		if (!result.isEmpty()) {
			Log.debug(c.getUserName() + " getMessages " + result);
		}
		return result;
	}

	/**
	 * Obtain UserContext of the specified user from ServletContext.
	 * 
	 * @param userName
	 * @return
	 */
	private UserContext getUserContext(final String userName) {
		return getUserContextMap().get(userName);
	}

	private synchronized Map<String, UserContext> getUserContextMap() {

		Map<String, UserContext> map = (Map<String, UserContext>) getServletContext()
				.getAttribute(ServerConstantes.USER_CONTEXT_MAP);
		if (null == map) {
			map = new Hashtable<String, UserContext>();
			getServletContext().setAttribute(ServerConstantes.USER_CONTEXT_MAP,
					map);
		}
		return map;
	}

	/**
	 * Obtain the user name of the session owner.
	 * 
	 * @return
	 */
	private String getUserName() {
		return (String) this.getThreadLocalRequest().getSession().getAttribute(
				ServerConstantes.USER_NAME);
	}

	public Map<String, EnumUserStatus> listUsers() throws ServiceException {
		accessUserContext();

		Map<String, EnumUserStatus> result = new HashMap<String, EnumUserStatus>();

		for (String user : getUserContextMap().keySet()) {
			UserContext c = getUserContextMap().get(user);

			result.put(c.getUserName(), c.getStatus());

		}
		return result;
	}

	public synchronized void login(final String userName)
			throws ServiceException {

		String oldUser = (String) this.getThreadLocalRequest().getSession()
				.getAttribute(ServerConstantes.USER_NAME);

		if (null != oldUser) {
			this.removeUserContext(oldUser);
		}

		UserContext conflictUserContext = getUserContext(userName);
		String sessionId = this.getThreadLocalRequest().getSession().getId();

		if (conflictUserContext != null) {
			// the user name is already used by the other person?
			if (!conflictUserContext.getSessionId().equals(sessionId)) {
				throw new ServiceException(
						ServiceException.USERNAME_ALREADY_IN_USE);
			}
		}

		this.getThreadLocalRequest().getSession().setAttribute(
				ServerConstantes.USER_NAME, userName);

		UserContext newContext = new UserContext(userName, sessionId);
		this.getUserContextMap().put(userName, newContext);

	}

	@Override
	public void logout(final String userName) throws ServiceException {
		this.getThreadLocalRequest().getSession().invalidate();

	}

	private synchronized void removeUserContext(final String userName) {
		UserContext c = this.getUserContextMap().remove(userName);
		if (c != null) {
			Log.debug("removed user=" + c.getUserName() + " session="
					+ c.getSessionId() + " lastconnect="
					+ new Date(c.getLastInteractionTime()));
		}

	}

	public void sendMessage(final AbstractMessage message)
			throws ServiceException {

		// assign the sender
		UserContext c = accessUserContext();
		message.setSender(c.getUserName());

		Log.debug(c.getUserName() + " sendMessage " + message);

		UserContext destination = getUserContext(message.getDestination());
		if (null == destination) {
			throw new ServiceException(
					ServiceException.DISTINATION_LOST_CONNECTION);
		}
		destination.addMessage(message);
	}

	public void updateUserStatus(final EnumUserStatus status)
			throws ServiceException {
		UserContext c = accessUserContext();
		Log.debug(c.getUserName() + " update status " + status);
		c.setStatus(status);
	}

}
